﻿
using System;
using System.Linq;
using Devtm.Generator;
using System.CodeDom;
using System.IO;
using System.Xml.Linq;
using Devtm.Generator.Writers;
using Devtm.Generator.Contexts;
using System.Reflection;
using System.Collections.Generic;
using Devtm.Writers;
using System.Text;
using System.Diagnostics;
using Devtm.Common;


namespace Devtm.Generator.Writers
{

    public class BaseXmlGenerator<TWriter, TContext> : Devtm.Generator.Generator<TWriter, TContext>, IRun
        where TWriter : IWriter, new()
        where TContext : IContext, new()
    {


        private Dictionary<string, MethodInfo> _dic = new Dictionary<string, MethodInfo>();
        private Stack<XElement> _currentXmlElement = new Stack<XElement>();
        private Stack<object> _currentObjectelements = new Stack<object>();
        private Dictionary<string, MappingPath> _mappings = new Dictionary<string, MappingPath>();

        public BaseXmlGenerator(TContext context)
            : base(context)
        {

            InitializeDic();

        }


        private void InitializeDic()
        {

            // _dic

            Type t = GetType();
            foreach (MethodInfo item in t.GetMethods())
            {

                if (item.DeclaringType == t && item.ReturnType == typeof(void) && item.Name == "Visit")
                {

                    var p = item.GetParameters().ToList();

                    if (p.Count() == 1)
                    {

                        _dic.Add(p[0].ParameterType.Name.ToLower(), item);

                    }

                }

            }

        }


        #region IRun implementation

        public virtual void Run(params object[] o)
        {


            XElement xdata = o[0] as XElement;


            if (o.Length >= 1)
                _mappings = o[1] as Dictionary<string, MappingPath>;


            NewWriter(Context.Namespace, Context.Filename, Context.Extend);
            
            Run(xdata);
            
            SaveWriter(Context.Namespace);


        }

        protected virtual void Run(XElement xdata)
        {
            PrivateVisit(xdata);
        }

        #endregion


        #region Visitor

        private int indent = 0;
        private void PrivateVisit(XElement xdata)
        {

            string str = SerializedAttribute(xdata);
            Log(Common.LogLevelEnm.Trace, str.PadLeft(indent, ' '), Tags.Parsing, Tags.Xml);
            indent++;


            MethodInfo methodInfo;
            object item;

            if (GetWrapper(xdata, out methodInfo, out item))
            {
                _currentObjectelements.Push(item);
                _currentXmlElement.Push(xdata);

                try
                {

                    methodInfo.Invoke(this, new object[] { item });

                }
                catch (Exception)
                {

                    throw;
                }
                finally
                {
                    indent--;
                    Log(Common.LogLevelEnm.Trace, string.Format("</{0}>", xdata.Name).PadLeft(indent, ' '), Tags.Parsing, Tags.Xml);
                }

                _currentXmlElement.Pop();
                _currentObjectelements.Pop();

            }
            else
            {
                Visit(xdata);
            }

        }

        private string SerializedAttribute(XElement xdata)
        {
            var n = xdata.ToString();
            n = n.Substring(0, n.IndexOf('>') + 1);
            return n;
        }


        private void VisitRecursif(XElement xdata)
        {

            foreach (var item in xdata.Elements())
                PrivateVisit(item);

        }


        protected virtual void Visit(XElement xdata)
        {
            VisitRecursif(xdata);
        }


        protected void Visit()
        {
            XElement ee = _currentXmlElement.Peek();
            VisitRecursif(ee);
        }

        #endregion


        #region Wrappers

        protected XElement CurrentXElement
        {
            get
            {
                return _currentXmlElement.Peek();
            }
        }

        protected T CurrentObject<T>()
            where T : class, new()
        {

            T result = default(T);

            foreach (var item in _currentObjectelements)
            {
                if (item is T)
                {
                    result = (T)item;
                    break;
                }
            }

            return result;
        }

        protected T GetWrapper<T>(XElement xdata)
            where T : class, new()
        {
            T result = new T();
            Map(xdata, result);
            return result;
        }

        protected bool GetWrapper(XElement xdata, out MethodInfo methodInfo, out object item)
        {

            methodInfo = null;
            item = null;

            string key = xdata.Name.LocalName.ToLower();

            if (_dic.ContainsKey(key))
            {

                methodInfo = _dic[key];
                Type type = methodInfo.GetParameters()[0].ParameterType;
                item = Activator.CreateInstance(type);

                Map(xdata, item);

                return true;

            }

            return false;

        }

        protected void Map(XElement xdata, object item)
        {

            try
            {

                Type type = item.GetType();

                foreach (XAttribute attribute in xdata.Attributes())
                {

                    string attrName = attribute.Name.LocalName.ToLower();
                    PropertyInfo prop = type.GetProperties().Where(c => c.Name.ToLower() == attrName).FirstOrDefault();

                    if (prop != null)
                    {

                        object _v;

                        if (prop.PropertyType == typeof(string))
                            _v = attribute.Value;

                        else
                        {
                            try
                            {
                                _v = Convert.ChangeType(attribute.Value, prop.PropertyType);
                            }
                            catch
                            {
                                throw new Exception(string.Format("target type {0} on the property {1} of {2} is not implemented", prop.PropertyType.Name, prop.Name, prop.DeclaringType.Name));
                            }
                        }

                        prop.GetSetMethod().Invoke(item, new object[] { _v });

                    }

                }

            }
            catch (Exception)
            {
                throw;
            }

        }

        #endregion

    }

}
